
//
// 版权所有 (c) 2021-2023 JR
//
// 特此免费授予获得本软件的任何人以处理本软件的权利，但须遵守以下条件：在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
//
// 软件按“原样”提供，不提供任何形式的明示或暗示的保证，包括但不限于对适销性、适用性和非侵权的保证。
// 在任何情况下，作者或版权持有人均不对任何索赔、损害或其他责任负责，无论是因合同、侵权或其他方式引起的，与软件或其使用或其他交易有关。

using Masuit.Tools;

namespace EMC.VISITOR.Core.Service;

/// <summary>
/// 系统菜单服务
/// </summary>
[ApiDescriptionSettings(Order = 450)]
public class SysMenuService : IDynamicApiController, ITransient
{
    private readonly UserManager _userManager;
    private readonly SqlSugarRepository<SysMenu> _sysMenuRep;
    private readonly SysRoleMenuService _sysRoleMenuService;
    private readonly SysUserRoleService _sysUserRoleService;
    private readonly SysCacheService _sysCacheService;
    private readonly SysCodeGenService _sysCodeGenService;
    private readonly CodeGenOptions _codeGenOptions;

    public SysMenuService(UserManager userManager,
        SqlSugarRepository<SysMenu> sysMenuRep,
        SysRoleMenuService sysRoleMenuService,
        SysUserRoleService sysUserRoleService,
        SysCacheService sysCacheService,
        SysCodeGenService sysCodeGenService,
        IOptions<CodeGenOptions> codeGenOptions)
    {
        _userManager = userManager;
        _sysMenuRep = sysMenuRep;
        _sysRoleMenuService = sysRoleMenuService;
        _sysUserRoleService = sysUserRoleService;
        _sysCacheService = sysCacheService;
        _sysCodeGenService = sysCodeGenService;
        _codeGenOptions = codeGenOptions.Value;
    }
    [DisplayName("获取App菜单")]
    public async Task<List<SysMenu>> GetAppMenus()
    {
        var appMenus = await _sysMenuRep.AsQueryable()
             .Where(u => u.Status == StatusEnum.Enable && u.IsAppShow == true)
             .Includes(r => r.ParentMenu)
             .OrderBy(u => new { u.OrderNo, u.Id })
             .ToListAsync();

        var appIds = appMenus.Select(u => u.Id).ToList();
        List<long> menuIdList = new List<long>();
        if (_userManager.SuperJR)
            menuIdList = appIds;
        else
        {
            var list = await GetMenuIdList();
            //找到按钮的上一级菜单
            var btnParentMenuIds = await _sysMenuRep.AsQueryable()
                .Where(r => r.Type == MenuTypeEnum.Btn && list.Contains(r.Id) &&
                SqlFunc.Subqueryable<SysMenu>().Where(c => c.Type == MenuTypeEnum.Menu && c.IsAppShow == true && c.Id == r.Pid).Any())
                 .Select(r => r.Pid).ToListAsync();
            menuIdList = appIds.Where(r => list.Contains(r)).ToList();
            if (btnParentMenuIds.Count > 0)
                menuIdList.AddRange(btnParentMenuIds);
        }
        var allShowAppMenus = appMenus.Where(r => r.Type == MenuTypeEnum.Menu && menuIdList.Contains(r.Id)).ToList();
        List<SysMenu> treeMenuList = allShowAppMenus.Select(r => r.ParentMenu).Distinct(r => r.Id).ToList();
        foreach (var parentMenu in treeMenuList)
        {
            parentMenu.Children.Clear();
            var apps = allShowAppMenus.Where(r => r.Pid == parentMenu.Id).ToList();
            apps.ForEach(c =>
            {
                c.AppIcon = c.AppIcon ?? "favorfill";
                c.Color = c.Color ?? "orange";
            });
            parentMenu.Children.AddRange(apps);
        }


        //var menus = await _sysMenuRep.AsQueryable().OrderBy(u => u.OrderNo).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray()); // 角色菜单授权时

        // 找到两级以上的目录，如：MES平台 （此种目录不需要加载到手机App）
        //var topDirs = menus.Where(r => r.Pid == 0 && r.IsAppShow != true).ToList();

        //if (topDirs.Count() > 0)
        //{
        //    topDirs.ForEach(r =>
        //    {
        //        if (r.Children.Any(c => c.IsAppShow == true))
        //            treeMenuList.AddRange(r.Children);
        //    });
        //    treeMenuList.AddRange(menus.Where(r => !topDirs.Select(c => c.Id).Contains(r.Id)).ToList());
        //}
        //else
        //    treeMenuList = menus;
        return treeMenuList.OrderBy(r => r.OrderNo).ToList();
    }

    /// <summary>
    /// 获取登录菜单树
    /// </summary>
    /// <returns></returns>
    [DisplayName("获取登录菜单树")]
    public async Task<List<MenuOutput>> GetLoginMenuTree()
    {
        if (_userManager.SuperJR)
        {
            var menuList = await _sysMenuRep.AsQueryable()
                .Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable)
                .OrderBy(u => new { u.OrderNo, u.Id }).ToTreeAsync(u => u.Children, u => u.Pid, 0);
            return menuList.Adapt<List<MenuOutput>>();
        }
        else
        {
            var menuIdList = await GetMenuIdList();
            var menuTree = await _sysMenuRep.AsQueryable()
                .Where(u => u.Status == StatusEnum.Enable)
                .OrderBy(u => new { u.OrderNo, u.Id }).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray());
            DeleteBtnFromMenuTree(menuTree);
            return menuTree.Adapt<List<MenuOutput>>();
        }
    }

    /// <summary>
    /// 删除登录菜单树里面的按钮
    /// </summary>
    private void DeleteBtnFromMenuTree(List<SysMenu> menuList)
    {
        if (menuList == null) return;
        for (var i = menuList.Count - 1; i >= 0; i--)
        {
            var menu = menuList[i];
            if (menu.Type == MenuTypeEnum.Btn)
                menuList.Remove(menu);
            else if (menu.Children.Count > 0)
                DeleteBtnFromMenuTree(menu.Children);
        }
    }

    /// <summary>
    /// 获取工作台需要显示的项次
    /// </summary>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "GetWorkBenchIconMenus"), HttpGet]
    public async Task<dynamic> GetWorkBenchIconMenus()
    {

        List<dynamic> list = new List<dynamic>();
        // 行显示5个
        int pageSize = 5;
        var codeGens = await _sysMenuRep.Context.MyQueryable<SysCodeGen>().ToListAsync();
        var pagedData = codeGens.ToPagedList(1, pageSize);
        for (int i = 1; i <= pagedData.TotalPages; i++)
        {
            var data = codeGens.ToPagedList(i, pageSize);
            list.Add(new
            {
                pageIndex = i,
                items = data.Items.Select(c => new { c.Id, c.BusName }).ToList()
            });
        }
        return list;
    }

    /// <summary>
    /// 获取菜单列表
    /// </summary>
    /// <returns></returns>
    [AllowAnonymous]
    [DisplayName("获取菜单列表")]
    public async Task<List<SysMenu>> GetList([FromQuery] MenuInput input)
    {
        var menuIdList = _userManager.SuperJR ? new List<long>() : await GetMenuIdList();

        // 有筛选条件时返回list列表（防止构造不出树）
        if (!string.IsNullOrWhiteSpace(input.Title) || input.Type is > 0)
        {
            return await _sysMenuRep.AsQueryable()
                .WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.Title.Contains(input.Title))
                .WhereIF(input.Type is > 0, u => u.Type == input.Type)
                .WhereIF(menuIdList.Count > 1, u => menuIdList.Contains(u.Id))
                .OrderBy(u => u.OrderNo).ToListAsync();
        }

        return _userManager.SuperJR ?
            await _sysMenuRep.AsQueryable().OrderBy(u => u.OrderNo).ToTreeAsync(u => u.Children, u => u.Pid, 0) :
            await _sysMenuRep.AsQueryable()
                .OrderBy(u => u.OrderNo).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray()); // 角色菜单授权时
    }

    /// <summary>
    /// 增加菜单
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Add"), HttpPost]
    [UnitOfWork(true)]
    [DisplayName("增加菜单")]
    public async Task AddMenu(AddMenuInput input)
    {
        var isExist = input.Type != MenuTypeEnum.Btn
            ? await _sysMenuRep.IsAnyAsync(u => u.Title == input.Title)
            : await _sysMenuRep.IsAnyAsync(u => u.Permission == input.Permission);

        if (isExist)
            throw Oops.Oh(ErrorCodeEnum.D4000);

        if (input.Name.IsNullOrEmpty() && input.Type == MenuTypeEnum.Menu)
            throw Oops.Oh(ErrorCodeEnum.D4009);

        // 校验菜单参数
        var sysMenu = input.Adapt<SysMenu>();
        CheckMenuParam(sysMenu);



        await _sysMenuRep.InsertAsync(sysMenu);
        // 清除缓存
        DeleteMenuCache();
    }

    [ApiDescriptionSettings(Name = "HandlerPageConfigCopy"), HttpPost]
    [DisplayName("复制页面配置")]
    public async Task<JRResult<string>> HandlerPageConfigCopy(UpdateMenuInput input)
    {
        if (input.Id == input.Pid)
            throw Oops.Oh(ErrorCodeEnum.D4008);

        if (input.CopyPageConfig.IsNullOrEmpty())
            return CustomeResult.Fail<string>("请输入需要配置页面的configPath！");

        var config = await _sysMenuRep.Context.Queryable<SysPageOptionConfig>()
              .Includes(r => r.column)
               .Where(r => r.configPath == input.CopyPageConfig.Trim()).FirstAsync();
        if (config == null)
            return CustomeResult.Fail<string>("未找到对应的配置！");

        if (!_sysMenuRep.Context.Queryable<SysPageOptionConfig>().Any(r => r.configPath == input.Component.Trim()))
        {
            config.Id = 0;
            config.configPath = input.Component.Trim();
            config.configCode = input.Name;
            config.configName = input.Title;
            config.column.ForEach(r =>
            {
                r.Id = 0;
                r.configPath = config.configPath;
            });
            await _sysMenuRep.Context.MyInsertable<SysPageOptionConfig>(config).ExecuteCommandAsync();
            await _sysMenuRep.Context.MyInsertable<SysPageOptionConfigColumn>(config.column).ExecuteCommandAsync();
        }
        return CustomeResult.Fail<string>("复制页面配置完成！");
    }

    /// <summary>
    /// 复制权限
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "HandlerCopy"), HttpPost]
    [DisplayName("复制权限")]
    public async Task<JRResult<string>> HandlerCopy(UpdateMenuInput input)
    {
        await UpdateMenu(input);
        await CopyAuth(input);
        return CustomeResult.Success<string>("复制权限成功！");
    }


    async Task CopyAuth(SysMenu menu)
    {
        if (!menu.CopyAuth.IsNullOrEmpty())
        {

            var target = await _sysMenuRep.AsQueryable().Where(r => r.Name == menu.CopyAuth.Trim()).FirstAsync();
            var menuAuths = await _sysMenuRep.AsQueryable().Where(r => r.Pid == target.Id).ToListAsync();
            for (int i = 0; i < menuAuths.Count; i++)
            {
                var auth = menuAuths[i];
                auth.Id = default(long);
                auth.Pid = menu.Id;
                auth.CreateTime = DateTime.Now;
                auth.UpdateTime = DateTime.Now;
                auth.Permission = $"{menu.Name}:{menuAuths[i].Permission.Split(':')[1]}";
            }
            //插入权限
            var _db = _sysMenuRep.AsQueryable().Context;
            var dbMenus = await _db.Queryable<SysMenu>().Where(r => r.Pid == menu.Id && menuAuths.Select(c => c.Permission).Contains(r.Permission)).ToListAsync();
            if (dbMenus.Count > 0)
            {

                dbMenus.ForEach(r =>
                {
                    var tempMenu = menuAuths.FirstOrDefault(c => c.Permission == r.Permission);
                    if (tempMenu != null)
                    {
                        r.IsDelete = false;
                        r.Title = tempMenu.Title;
                        r.Type = tempMenu.Type;
                        r.OrderNo = tempMenu.OrderNo;
                    }
                });
                //已存在的按钮
                await _db.Updateable<SysMenu>(dbMenus).ExecuteCommandAsync();
                //修改已存在的按钮权限
                await _db.Updateable<SysRoleMenu>()
                    .SetColumns(r => new SysRoleMenu { IsDelete = false })
                    .Where(r => dbMenus.Select(c => c.Id).Contains(r.MenuId)).ExecuteCommandAsync();
                //取差集
                menuAuths = menuAuths.Where(r => !dbMenus.Select(c => c.Permission).Contains(r.Permission)).ToList();
            }
            if (menuAuths.Count > 0)
                await _db.Insertable(menuAuths).ExecuteCommandAsync();

            //await _sysMenuRep.AsQueryable().Context.Deleteable<SysMenu>(r => r.Pid == menu.Id && menuAuths.Select(c => c.Title).Contains(r.Title)).ExecuteCommandAsync();
            //await _sysMenuRep.InsertRangeAsync(menuAuths);

            var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "views", "main");

            //复制文件夹
            var source = Path.Combine(frontendPath, menu.CopyAuth);
            var targetPath = Path.Combine(frontendPath, menu.Name);
            if (File.Exists(source))
                FileHelper.CopyDir(source, targetPath);
            else
                throw Oops.Oh(ErrorCodeEnum.D4010);
            //复制前端UI
            var fileDirPath = Path.Combine(frontendPath, menu.Name[..1].ToLower() + menu.Name[1..]);//
            List<string> allFilePaths = new List<string>();
            FileHelper.GetAllFileAndDirectory(fileDirPath, allFilePaths, true, true, false); //文件夹路径不要
            foreach (var filePath in allFilePaths)
            {
                var readString = FileHelper.ReadFile(filePath, Encoding.UTF8);
                readString = readString
                    .Replace($"{target.Name}:", $"{menu.Name}:")
                    .Replace($"/{target.Name}/", $"/{menu.Name}/")
                    .Replace($"/api/main/{target.Name}", $"/api/main/{menu.Name}");
                FileHelper.WriteFile(filePath, readString, Encoding.UTF8, true);
            }
            //复制前端api.ts
            var apiJsPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "api", "main", target.Name[..1].ToLower() + target.Name[1..] + ".ts");
            targetPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "api", "main", menu.Name[..1].ToLower() + menu.Name[1..] + ".ts");
            var readString1 = FileHelper.ReadFile(apiJsPath, Encoding.UTF8);
            readString1 = readString1
                .Replace($"{target.Name}:", $"{menu.Name}:")
                .Replace($"/{target.Name}/", $"/{menu.Name}/");
            FileHelper.WriteFile(targetPath, readString1, Encoding.UTF8, true);

            //复制后端Service.cs
            var codeGen = App.GetOptions<CodeGenOptions>();
            var servicePath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, codeGen.BackendApplicationNamespace, "Service", "ACopy");
            var sourceServicePath = Path.Combine(servicePath, "Template.txt");
            var targetServicePath = Path.Combine(servicePath, menu.Name[..1].ToUpper() + menu.Name[1..] + "Service.cs");
            var readString2 = FileHelper.ReadFile(sourceServicePath, Encoding.UTF8);
            readString2 = readString2
                .Replace("Source", target.Name[..1].ToUpper() + target.Name[1..])
                .Replace("Target", menu.Name[..1].ToUpper() + menu.Name[1..]);
            FileHelper.WriteFile(targetServicePath, readString2, Encoding.UTF8, true);
        }
    }
    /// <summary>
    /// 更新菜单
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Update"), HttpPost]
    [UnitOfWork(true)]
    [DisplayName("更新菜单")]
    public async Task UpdateMenu(UpdateMenuInput input)
    {
        if (input.Id == input.Pid)
            throw Oops.Oh(ErrorCodeEnum.D4008);

        var isExist = input.Type != MenuTypeEnum.Btn
            ? await _sysMenuRep.IsAnyAsync(u => u.Title == input.Title && u.Type == input.Type && u.Id != input.Id)
            : await _sysMenuRep.IsAnyAsync(u => u.Permission == input.Permission && u.Type == input.Type && u.Id != input.Id);
        if (isExist)
            throw Oops.Oh(ErrorCodeEnum.D4000);

        if (input.Name.IsNullOrEmpty() && input.Type == MenuTypeEnum.Menu)
            throw Oops.Oh(ErrorCodeEnum.D4009);
        // 校验菜单参数
        var sysMenu = input.Adapt<SysMenu>();
        CheckMenuParam(sysMenu);

        await _sysMenuRep.AsUpdateable(sysMenu).ExecuteCommandAsync();
        // 清除缓存
        DeleteMenuCache();
    }

    /// <summary>
    /// 删除菜单
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [UnitOfWork]
    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
    [DisplayName("删除菜单")]
    public async Task DeleteMenu(DeleteMenuInput input)
    {
        var menuTreeList = await _sysMenuRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true);
        var menuIdList = menuTreeList.Select(u => u.Id).ToList();

        await _sysMenuRep.DeleteAsync(u => menuIdList.Contains(u.Id));

        // 级联删除角色菜单数据
        await _sysRoleMenuService.DeleteRoleMenuByMenuIdList(menuIdList);

        // 清除缓存
        DeleteMenuCache();
    }

    /// <summary>
    /// 增加和编辑时检查菜单数据
    /// </summary>
    /// <param name="menu"></param>
    private static void CheckMenuParam(SysMenu menu)
    {
        var permission = menu.Permission;
        if (menu.Type == MenuTypeEnum.Btn)
        {
            menu.Name = null;
            menu.Path = null;
            menu.Component = null;
            menu.Icon = null;
            menu.Redirect = null;
            menu.OutLink = null;
            menu.IsHide = false;
            menu.IsKeepAlive = true;
            menu.IsAffix = false;
            menu.IsIframe = false;

            if (string.IsNullOrEmpty(permission))
                throw Oops.Oh(ErrorCodeEnum.D4003);
            if (!permission.Contains(':'))
                throw Oops.Oh(ErrorCodeEnum.D4004);
        }
        else
        {
            menu.Permission = null;
        }
    }

    /// <summary>
    /// 获取用户拥有按钮权限集合（缓存）
    /// </summary>
    /// <returns></returns>
    [DisplayName("获取按钮权限集合")]
    public async Task<List<string>> GetOwnBtnPermList()
    {
        var userId = _userManager.UserId;
        var permissions = _sysCacheService.Get<List<string>>(CacheConst.KeyUserButton + userId);
        if (permissions == null || permissions.Count == 0)
        {
            List<long> menuIdList = new List<long>();
            if (_userManager.SuperJR)
                menuIdList = new List<long>();
            else
                menuIdList = await GetMenuIdList();

            permissions = await _sysMenuRep.AsQueryable()
                .Where(u => u.Type == MenuTypeEnum.Btn)
                .WhereIF(menuIdList.Count > 0, u => menuIdList.Contains(u.Id))
                .Select(u => u.Permission).ToListAsync();
            _sysCacheService.Set(CacheConst.KeyUserButton + userId, permissions);
        }
        return permissions;
    }

    /// <summary>
    /// 获取App按钮权限集合（缓存）
    /// </summary>
    /// <returns></returns>
    [DisplayName("获取App按钮权限集合")]
    public async Task<dynamic> GetAppOwnBtnPermList()
    {
        var userId = _userManager.UserId;
        var permissions = _sysCacheService.Get<dynamic>(CacheConst.KeyUserAppButton + userId);
        if (permissions == null)
        {
            var menuIdList = _userManager.SuperJR ? new List<long>() : await GetMenuIdList();
            permissions = await _sysMenuRep.AsQueryable()
                .Where(u => u.Type == MenuTypeEnum.Btn)
                .Where(r => SqlFunc.Subqueryable<SysMenu>().Where(c => c.IsAppShow == true && c.Id == r.Pid).Any())
                .WhereIF(menuIdList.Count > 0, u => menuIdList.Contains(u.Id))
                .Select(u => new { u.Permission, u.Title, u.LinkUrl }).ToListAsync();
            _sysCacheService.Set(CacheConst.KeyUserAppButton + userId, permissions);
        }
        return permissions;
    }

    /// <summary>
    /// 获取系统所有按钮权限集合（缓存）
    /// </summary>
    /// <returns></returns>
    [NonAction]
    public async Task<List<string>> GetAllBtnPermList()
    {
        var permissions = _sysCacheService.Get<List<string>>(CacheConst.KeyUserButton + 0);
        if (permissions == null || permissions.Count == 0)
        {
            permissions = await _sysMenuRep.AsQueryable()
                .Where(u => u.Type == MenuTypeEnum.Btn)
                .Select(u => u.Permission).ToListAsync();
            _sysCacheService.Set(CacheConst.KeyUserButton + 0, permissions);
        }
        return permissions;
    }

    /// <summary>
    /// 清除菜单和按钮缓存
    /// </summary>
    private void DeleteMenuCache()
    {
        _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserMenu);
        _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserButton);
    }

    /// <summary>
    /// 获取当前用户菜单Id集合
    /// </summary>
    /// <returns></returns>
    private async Task<List<long>> GetMenuIdList()
    {
        var roleIdList = await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId);
        var dataList = await _sysRoleMenuService.GetRoleMenuIdList(roleIdList);
        return dataList;
    }
}